package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws IOException {
        /**
         * Selector
         * 多路复用器，用于监听多个 Channel 的 IO 事件。一个线程可管理多个连接，避免为每个连接创建单独线程。
         * ServerSocketChannel
         * 服务端套接字通道，是 NIO 对传统ServerSocket的升级，支持非阻塞模式。
         * SelectionKey
         * 表示Channel和Selector之间的注册关系，包含四种事件类型：
         * OP_ACCEPT：新连接到来
         * OP_READ：通道可读
         * OP_WRITE：通道可写
         * OP_CONNECT：连接已建立
         */
        // 创建Selector和ServerSocketChannel
        Selector selector = Selector.open();
        //创建一个新的ServerSocketChannel实例。
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定到本地 8080 端口，监听客户端连接。
        serverSocketChannel.bind(new InetSocketAddress(8080));
        //设置为非阻塞模式。此时accept()方法不会阻塞，若无连接则返回null。
        serverSocketChannel.configureBlocking(false);
        //将通道注册到Selector，并监听OP_ACCEPT事件（新连接）。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("NIO服务器启动，监听端口: 8080");

        while (true) {
            // 阻塞等待就绪的Channel
            //阻塞方法，直到至少有一个注册的通道有就绪事件（如新连接或可读数据）。返回就绪事件的数量。
            selector.select();

            // 处理就绪的Channel,返回所有就绪事件的SelectionKey集合。
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();

                if (key.isAcceptable()) {
                    // 处理新连接
                    /**
                     * 接受新连接，返回SocketChannel（对应客户端连接）。非阻塞模式下，若无连接则返回null，但此处因selector.select()确保有就绪事件，故不会返回null。
                     */
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置客户端通道为非阻塞模式，read()和write()方法不会阻塞。
                    socketChannel.configureBlocking(false);
                    //将客户端通道注册到Selector，监听OP_READ事件（数据可读）。
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("新连接: " + socketChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 处理读取事件
                    /**
                     * 1. ByteBuffer 核心类
                     * NIO 中用于数据读写的缓冲区，有三种模式：
                     * 写模式（默认）：通过put()写入数据
                     * 读模式：通过flip()切换，通过get()读取数据
                     * 重置模式：通过clear()或compact()重置，准备下次写入
                     * 2. 关键操作
                     * ByteBuffer.allocate(1024)
                     * 创建容量为 1024 字节的缓冲区。
                     * socketChannel.read(buffer)
                     * 从通道读取数据到缓冲区，返回读取的字节数：
                     * >0：成功读取数据
                     * 0：无数据可读
                     * -1：连接已关闭
                     * buffer.flip()
                     * 将缓冲区从写模式切换为读模式（重置position和limit指针）。
                     * buffer.remaining()
                     * 返回缓冲区中剩余可读取的字节数。
                     * socketChannel.write(response)
                     * 将响应数据写入通道。注意：非阻塞模式下，需循环写入直到全部数据发送完毕（本例简化处理）。
                     */
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = socketChannel.read(buffer);

                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String message = new String(data, StandardCharsets.UTF_8);
                        System.out.println("收到消息: " + message);

                        // 回写响应
                        ByteBuffer response = ByteBuffer.wrap(("服务器响应: " + message).getBytes());
                        socketChannel.write(response);
                    } else if (bytesRead == -1) {
                        // 连接关闭
                        socketChannel.close();
                        System.out.println("连接关闭");
                    }
                }

                keyIterator.remove();
            }
        }
    }
}
